package edu.unlv.cs.ladders.services;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.acls.domain.BasePermission;
import org.springframework.security.acls.domain.GrantedAuthoritySid;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.domain.PrincipalSid;
import org.springframework.security.acls.model.Acl;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import edu.unlv.cs.ladders.entities.*;

@Service
public class SpringSecurityPermissionService implements PermissionService {
	private static final Logger LOG = Logger
			.getLogger(SpringSecurityPermissionService.class);
	private static final String COULD_NOT_FIND_METHOD = "Could not find a method for setting default permissions.";
	private static final String NULL_OBJECT_ERROR = "unable to set the permissions on a null object.";

	public void setDefaultPermissions(Object entity) {
		try {
			Method method = this.getClass().getDeclaredMethod(
					"setEntityPermissions", entity.getClass());
			method.invoke(this, entity);
			grantPermissionsToAdmin(entity);
			
		} catch (SecurityException e) {
			LOG.error("Security exception while setting permissions.", e);
			throw e;
		} catch (NoSuchMethodException e) {
			assert false : "Setting permission for unknown entity.";
			LOG.error(COULD_NOT_FIND_METHOD, e);
			throw new RuntimeException(e);
		} catch (IllegalArgumentException e) {
			assert false : "Setting permission for unknown entity.";
			LOG.error(COULD_NOT_FIND_METHOD, e);
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			assert false : "Setting permission for unknown entity.";
			LOG.error(COULD_NOT_FIND_METHOD, e);
			throw new RuntimeException(e);
		} catch (InvocationTargetException e) {
			assert false : "Setting permission for unknown entity.";
			LOG.error(COULD_NOT_FIND_METHOD, e);
			throw new RuntimeException(e);
		}
	}

	/**
	 * @param entity
	 */
	private void grantPermissionsToAdmin(Object entity) {
		this.grantPermission(BaseEntity.class.cast(entity), UserRole.ROLE_ADMIN, BasePermission.ADMINISTRATION);
		this.grantPermission(BaseEntity.class.cast(entity), UserRole.ROLE_ADMIN, BasePermission.WRITE);
		this.grantPermission(BaseEntity.class.cast(entity), UserRole.ROLE_ADMIN, BasePermission.DELETE);
		this.grantPermission(BaseEntity.class.cast(entity), UserRole.ROLE_ADMIN, BasePermission.WRITE);
		this.grantPermission(BaseEntity.class.cast(entity), UserRole.ROLE_ADMIN, BasePermission.CREATE);
	}

	@SuppressWarnings("unused")
	private void setEntityPermissions(final Challenge challenge) {
		assert challenge != null : NULL_OBJECT_ERROR;
		if (challenge == null) {
			throw new IllegalArgumentException(NULL_OBJECT_ERROR);
		}
		Participant currentUser = Participant.getCurrentUser();
		this.grantPermission(challenge, currentUser,
				BasePermission.ADMINISTRATION);

		Team challenger = challenge.getChallengerTeam();
		for (Participant challengerTeamMember : challenger.getMembers()) {
			this.grantPermission(challenge, challengerTeamMember,
					BasePermission.WRITE);
		}
		Team defender = challenge.getDefenderTeam();
		for (Participant defenderTeamMember : defender.getMembers()) {
			this.grantPermission(challenge, defenderTeamMember,
					BasePermission.WRITE);
		}
	}

	@SuppressWarnings("unused")
	private void setEntityPermissions(final Event event) {
		assert event != null : NULL_OBJECT_ERROR;
		if (event == null) {
			throw new IllegalArgumentException(NULL_OBJECT_ERROR);
		}
		Team challenger = event.getChallenger();
		Team defender = event.getDefender();
		Set<Participant> members = new HashSet<Participant>();
		members.addAll(challenger.getMembers());
		members.addAll(defender.getMembers());
		for (Participant participant : members) {
			this.grantPermission(event, participant, BasePermission.WRITE);
		}
	}

	@SuppressWarnings("unused")
	private void setEntityPermissions(final Participant participant) {
		assert participant != null : NULL_OBJECT_ERROR;
		if (participant == null) {
			throw new IllegalArgumentException(NULL_OBJECT_ERROR);
		}
		this.grantPermission(participant, participant, BasePermission.WRITE);
		this.grantPermission(participant, UserRole.ROLE_PARTICIPANT, BasePermission.READ);
	}

	@SuppressWarnings("unused")
	private void setEntityPermissions(final Score score) {
		assert score != null : NULL_OBJECT_ERROR;
		if (score == null) {
			throw new IllegalArgumentException(NULL_OBJECT_ERROR);
		}

		Participant currentUser = Participant.getCurrentUser();
		this.grantPermission(score, currentUser, BasePermission.ADMINISTRATION);

		Event event = score.getEvent();

		Team challenger = event.getChallenger();
		Team defender = event.getDefender();
		Set<Participant> members = new HashSet<Participant>();
		members.addAll(challenger.getMembers());
		members.addAll(defender.getMembers());
		for (Participant participant : members) {
			this.grantPermission(score, participant, BasePermission.WRITE);
		}
	}

	@SuppressWarnings("unused")
	private void setEntityPermissions(final Tag tag) {
		assert tag != null : NULL_OBJECT_ERROR;
		if (tag == null) {
			throw new IllegalArgumentException(NULL_OBJECT_ERROR);
		}

	}

	@SuppressWarnings("unused")
	private void setEntityPermissions(final Team team) {
		assert team != null : NULL_OBJECT_ERROR;
		if (team == null) {
			throw new IllegalArgumentException(NULL_OBJECT_ERROR);
		}
		Participant currentParticipant = Participant.getCurrentUser();
		this.grantPermission(team, currentParticipant,
				BasePermission.ADMINISTRATION);

		for (Participant participant : team.getMembers()) {
			this.grantPermission(team, participant, BasePermission.WRITE);
		}
	}

	@SuppressWarnings("unused")
	private void setEntityPermissions(final Tournament tournament) {
		assert tournament != null : NULL_OBJECT_ERROR;
		if (tournament == null) {
			throw new IllegalArgumentException(NULL_OBJECT_ERROR);
		}

	}
	public void grantPermission(BaseEntity entity, UserDetails userDetails,
			Permission permission) {
		Sid sid = new PrincipalSid(userDetails.getUsername());
		this.grantPermissionToSid(entity, sid, permission);
	}
	public void grantPermission(BaseEntity entity, String role, Permission permission){
		GrantedAuthority grantedAuthority = new GrantedAuthorityImpl(role);
		Sid sid = new GrantedAuthoritySid(grantedAuthority);
		this.grantPermissionToSid(entity, sid, permission);
	}
	@Transactional
	private void grantPermissionToSid(BaseEntity entity, Sid sid, Permission permission){
		LOG.info("Granting " + permission + " on class:"+entity.getClass().getName()+" of id "+ entity.getId() +" to " + sid);
		ObjectIdentity oi = new ObjectIdentityImpl(entity.getClass(), entity
				.getId());
		// Create or update the relevant ACL
		MutableAcl acl = null;
		try {
			acl = (MutableAcl) getAclService().readAclById(oi);
		} catch (NotFoundException e) {
			acl = getAclService().createAcl(oi);
		}

		// Now grant some permissions via an access control entry (ACE)
		acl.insertAce(acl.getEntries().size(), permission, sid, true);
		getAclService().updateAcl(acl);
	}
	private MutableAclService aclService;

	public MutableAclService getAclService() {
		return this.aclService;
	}

	@Autowired
	public void setAclService(MutableAclService aclService) {
		this.aclService = aclService;
	}
	
	public boolean currentUserHasWritePermission(BaseEntity entity){
		Permission writePermission = BasePermission.WRITE;
		ObjectIdentity oi = new ObjectIdentityImpl(entity.getClass(), entity
				.getId());
		Acl acl = this.getAclService().readAclById(oi);
		List<Permission> permissions = new ArrayList<Permission>();
		permissions.add(writePermission);
		Participant currentUser = Participant.getCurrentUser();
		List<Sid> sids = new ArrayList<Sid>();
		for(GrantedAuthority authority:currentUser.getAuthorities()){
			sids.add(new GrantedAuthoritySid(authority));
		}
		sids.add(new PrincipalSid(currentUser.getUsername()));
		return acl.isGranted(permissions, sids, false);
	}
	
}
